import ast
import csv
import functools
import json
import random
from gmssl import sm2
import requests
from base64 import b64encode
import os
from datetime import datetime
import time

from subsidy.app.flashClass.app.userAgent import generate_user_agents
from subsidy.app.flashClass.app.dbHelp import DbHelp
from subsidy.app.flashClass.app.utils import Utils
from subsidy.app.config import Config
import redis

"""江西省补贴性线上职业技能培训管理平台(刷课系统)"""
class FlashClass(DbHelp):
    def __init__(self, idCar, pwd, id):
        self.logger = self.loggerHandlerInit("flashClass")
        super().__init__(self.logger)  # 初始化数据库
        self.idCar = idCar
        self.pwd = pwd
        self.id = id
        self.session = requests.Session()
        self.UrlPool = self.iniApi()
        self.requestErrorTime = 5 # 统一重新请求时间
        self.accessToken = ""
        self.allDoneClass = [] # 未完成课程

    def check_file_and_redis(self, msg=None):
        def iniApi(func):
            def wrapper(self, *args, **kwargs):
                # 这里可以添加根据Redis中的数据进行进一步判断的逻辑，比如检查某个键的值
                redis_value = self.readDb.get(self.id).decode("utf-8")
                # print(f'根据Redis数据{redis_value}，判断是否执行。')
                if redis_value is None:
                    print('根据Redis数据，不执行被装饰函数并结束运行。')
                    return
                elif redis_value == "1":
                    print('根据Redis数据，不执行被装饰函数并结束运行。')
                    return
                return func(self, *args, **kwargs)
            return wrapper
        return iniApi

    def loggerHandlerInit(self,logFileName):
        """初始化日志处理器"""
        import logging
        logger = logging.getLogger(logFileName)
        logger.setLevel(logging.DEBUG)
        file_handler = logging.FileHandler(os.path.basename(__file__).replace(".py", ".log"), encoding="utf-8")
        console_handler = logging.StreamHandler()
        logger.addHandler(file_handler)
        logger.addHandler(console_handler)
        return logger

    def calculate_execution_time(self, msg):
        """时间计时器"""
        def iniApi(func):
            def wrapper(self, *args, **kwargs):
                if msg:
                    self.logger.info(f"方法名 {func.__name__}: 时间: {self.getNowTime()}, info: 正在初始化 {msg}...")
                else:
                    self.logger.info(f"方法名 {func.__name__}: 时间: {self.getNowTime()}, info: 正在初始化 {func.__name__}...")
                start_time = time.time()
                result = func(self, *args, **kwargs)
                end_time = time.time()
                execution_time = (end_time - start_time)
                self.logger.info(f"方法名 {func.__name__}: 时间: {self.getNowTime()}, info: {msg if msg else func.__name__}加载完成,耗时时间为: {execution_time:.3f} 秒")
                return result
            return wrapper
        return iniApi

    @check_file_and_redis(1, "")
    def test(self):
        print("我执行了..")

    @check_file_and_redis(1, "")
    def getNowTime(self):
        """获取系统当前时间"""
        current_datetime = datetime.now()
        formatted_date_time = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
        return formatted_date_time

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "发送请求获取响应")
    def guaranteeRequest(self, method, url, **kwargs):
        """保证每次的请求都是正常"""
        try:
            if method == 'GET':
                response = self.session.get(url, **kwargs)
                self.logger.info(f"当前: {url} 请求成功")
                return response
            elif method == 'POST':
                response = self.session.post(url, **kwargs)
                self.logger.info(f"当前: {url} 请求成功")
                return response
            else:
                self.logger.debug(f"不支持的请求方法: {method}")
                raise ValueError(f"不支持的请求方法: {method}")
        except Exception as e:
            self.logger.debug(f"当前: {url} 请求出现异常{e}, 正在等待5秒后重试....")
            self.unifiedErrorSleep()  # 等待时间后后重新请求
            return self.guaranteeRequest(method, url, **kwargs)  # 如果获取失败则重新获取

    @check_file_and_redis(1, "")
    @calculate_execution_time(1,"Url地址池")
    def iniApi(self):
        """初始化Url地址池"""
        return {
            "loginPublicUrl": "https://api.cloud.wozhipei.com/auth/user/v1/public_key", # 获取公钥
            "loginUrl": "https://api.cloud.wozhipei.com/auth/user/v1/login", # 用户登录认证
            "updateTokenUrl": "https://apif.wozhipei.com/users/update-access-token", # 更新Token信息
            "studyUrl": "https://apif.wozhipei.com/student-center/study", # 获取课程列表
            "previewUrl": "https://apif.wozhipei.com/courses/test-preview?course_id={courseId}&class_id={class_id}", # 获取课程列表
            "updateProgress":"https://apistudy.wozhipei.com/studies/study?video_id={video_id}&u={u}&time={time}&unit_id={unit_id}&class_id={class_id}",
            "videDetails": "https://apif.wozhipei.com/course-units/{unit_id}?class_id={class_id}"
        }

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "获取加密公钥")
    def getPublicKey(self):
        """获取公钥"""
        self.setSessionHeaders({
            'User-Agent': random.choice(generate_user_agents(100))
        })
        response = self.guaranteeRequest("GET", self.UrlPool.get("loginPublicUrl"), headers=self.session.headers)
        if response.json()['messageCode'] == "200":
            publicKey = response.json()['data']
            self.logger.info(f"{self.getNowTime()}, 登录接口的公钥获取完毕。")
            return publicKey
        else:
            """5秒后重新获取Key"""
            self.unifiedErrorSleep()
            self.getPublicKey()

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "统一添加请求头信息")
    def setSessionHeaders(self, items):
        """统一添加请求头信息"""
        for k, v in items.items():
            self.session.headers[k] = v

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "统一错误请求等待时间")
    def unifiedErrorSleep(self):
        """统一错误请求等待时间"""
        time.sleep(self.requestErrorTime)

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "用户登录获取")
    def getLoginToken(self, publicKey):
        """用户登录获取tokne"""
        def encrypt():
            data_str = self.pwd
            data_bytes = data_str.encode('utf-8')
            encoded_data = b64encode(data_bytes)
            sm2_crypt = sm2.CryptSM2(public_key=publicKey, private_key="", mode=1)
            return sm2_crypt.encrypt(encoded_data).hex()
        encryptData = encrypt()
        data = {
            "account": self.idCar,
            "appKey": "WEB",
            "sid": 1018,
            "type": 1,
            "authOpenId": "",
            "authType": "",
            "publicKey": publicKey,
            "password": encryptData
        }
        response = self.guaranteeRequest("POST", self.UrlPool.get("loginUrl"), headers=self.session.headers, json=data)
        data = response.json()
        if data['messageCode'] == "200":
            accessToken = data['data']['accessToken']
            self.accessToken = accessToken # 设置全局token
            self.setSessionHeaders({
                'Authorization': f"Bearer {accessToken}"
            })
            return f"Bearer {accessToken}"
        elif data['messageCode'] == "1001":
            # 账号或密码错误
            return 1001
        else:
            self.logger.debug(f"当前: 登录失败 响应结果:{data}\n 请求信息{data} 正在等待5秒后重试....")
            return data['messageCode']

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "获取解析课程安排")
    def getStudentStudy(self):
        """获取课程安排"""
        headers = {
            'Host': 'apif.wozhipei.com',
            'Origin': 'https://admin-jiangxi.zhipeizaixian.com',
            'X-User-Type': '1'
        }
        newHeaders = {**self.session.headers, **headers}

        response = self.guaranteeRequest("GET", self.UrlPool['studyUrl'], headers=newHeaders)
        if response.status_code == 200:
            studentStudy = response.json()
            uid = studentStudy[0]['uid'] # uuid
            class_id = studentStudy[0]['courseStat']['on']["course"][0]['class_id'] # 教室Id
            courseId = studentStudy[0]['courseStat']['on']["course"][0]['course']['id'] # 课程ID
            return {
                "uid":uid,
                "class_id":class_id,
                "courseId":courseId
            }
        else:
            self.logger.debug(f"当前: 获取课程安排失败 响应结果:{response.content} 正在等待5秒后重试....")
            self.logger.debug(f"当前: 获取课程安排失败 头:{newHeaders} 正在等待5秒后重试....")
            self.unifiedErrorSleep()
            self.getStudentStudy()

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "获取视频详情")
    def getVideDetaile(self,unit_id, class_id):
        """获取视频详情"""
        headers = {
            "Host": "apif.wozhipei.com",
            "Origin": "https://admin-jiangxi.zhipeizaixian.com",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "cross-site",
            "X-Client-Type": "pc",
            "X-Site-Alias": "jx",
            "X-User-Type": "1",
        }
        newHeaders = {**self.session.headers, **headers}

        response = self.guaranteeRequest("GET",self.UrlPool.get("videDetails").format(
            unit_id=unit_id,
            class_id=class_id
        ), headers=newHeaders)
        if response.status_code == 200:
            details = response.json()
            vide_id = details['video']['id']
            return vide_id

        else:
            content = response.content.decode("utf-8")
            self.logger.debug(f"当前: 获取视频详情失败 响应结果:{content} 正在等待5秒后重试....")
            self.unifiedErrorSleep()
            self.getVideDetaile(unit_id, class_id)

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "获取全部课组")
    def getAllcoursesPreview(self, classInfo):
        """获取全部课组"""
        def analysisPreview(classDetile, class_id):
            courses = classDetile["course"]
            print(courses)
            for course in courses:
                # 每一组，有两层, 第二层才是刷课数据
                # print(f"当前正在刷的是 {course['title']} 分组")
                for unit in course['units']:
                    progress_time = unit['progress_time']  # 当前播放进度时常
                    total_time = unit['total_time']  # 总进度时常
                    title = unit['title']  # 视频名称
                    chapter_id = unit['chapter_id']  # 章节 ID
                    id = unit['id']  # 视频id
                    video_id = self.getVideDetaile(id,classInfo.get('class_id'))
                    alldone = {
                        "idCar": self.idCar, # 视频者的账号
                        "uid": classInfo.get("uid"), # 更新进度Id
                        "id": id, # 课程Id
                        "chapter_id": chapter_id, # 章节 ID
                        "title": title, # 课程名称
                        "progress_time": progress_time, # 当前播放进度
                        "total_time":total_time, #总进度
                        "video_id":video_id, #视频Id
                        "class_id":class_id, #教室Id
                    }
                    self.allDoneClass.append(alldone) # 保存所有进度

            self.delAllDbProgress(self.idCar) # 所有进度入库前先删除所有数据
            self.saveDbProgress(self.allDoneClass) # 所有进度入库

        headers = {
            "Origin": "https://admin-jiangxi.zhipeizaixian.com",
            "X-User-Type": "1",
        }
        newHeaders = {**self.session.headers, **headers}
        response = self.guaranteeRequest("GET",
                                         self.UrlPool['previewUrl'].format(
                                                 courseId=classInfo.get('courseId'),
                                                 class_id=classInfo.get('class_id')
                                         ),
                                         headers=newHeaders)
        if response.status_code == 200:
            data = response.json()
            analysisPreview(data, classInfo.get('class_id'))
        else:
            self.logger.debug(f"当前: 获取课程组失败 响应结果:{response.content.decode('utf-8')} 正在等待5秒后重试....")
            self.unifiedErrorSleep()
            self.getAllcoursesPreview(classInfo)

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "实现课程进度修改")
    def saveDbProgress(self, info: []):
        """将数据存到数据库, 单独实现课程进度修改"""
        if len(info) > 0:
            self.saveVideoProgress(info)


    def setRedisInfo(self, info):
        self.readDb.set(self.id + ":info", info)

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "更新视频进度")
    def updateProgress(self):
        """
            更新视频进度 考虑直接使用多线程进行刷的话大概率会被封号,还是单个刷吧
            视频更新前先把数据保存在数据库里面
        """
        def setProgressVideo(info:dict): # total_time = 2872 progress_time = 2340
            # 插入本地时间
            one_videoDetail = self.querOneVideoDeatils(self.idCar, video_id)
            progress_time = one_videoDetail.get("progress_time")
            sum = 0

            while int(info.get("total_time")) >= int(info.get("progress_time")):
                useStatus = self.readDb.get(self.id).decode("utf-8")
                if useStatus=='1':
                    self.logger.info(f"结束刷课")
                    break
                queryDate = self.query_sleep_time({
                    "video_id": video_id,
                    "idCar": self.idCar
                })
                is60Seconds, seconds = Utils.check_local_time_exceed_60_seconds(queryDate[0].get("time"))
                # 判断是否超过60秒，如果没有的话强制等待
                if not is60Seconds:
                    """防止已经更新过进度,间隔不到60的, 需要等待60秒再开始"""
                    seconds = 60 - seconds
                    print("当前剩余实际按: ", seconds)
                    while seconds > 0:
                        seconds -= 1
                        self.logger.info(f"防止已经更新过进度,间隔不到60的, 需要等待60秒再开始, 当前剩余时间{seconds}")
                        self.setRedisInfo(f"防止已经更新过进度,间隔不到60的, 需要等待60秒再开始, 当前剩余时间{seconds}")
                        time.sleep(1)


                total_time = Utils.getseconds_time(info.get("total_time"))
                difference = int(info.get("total_time")) - int(info['progress_time'])
                self.logger.info(f"total_time: {info.get('total_time')}, progress_time: {progress_time}, 相差: {difference}")
                self.setRedisInfo(f"total_time: {info.get('total_time')}, progress_time: {progress_time}, 相差: {difference}")
                progressTime = Utils.getseconds_time(info.get("progress_time"))

                if difference > 60:
                    progress_time += 1

                else:
                    progress_time += difference

                sum+=1
                time.sleep(1)
                self.logger.info(f"当前视频时常是: {total_time}, 当前已经到刷了 {progress_time}， 已经播放了{sum}秒。"
                                 f"更新进度时间是: {Config.EVERY_OTHER_PROGRESSVIDEOTIME}, 已经累加进度{ progress_time}")
                self.setRedisInfo(f"当前视频时常是: {total_time}, 当前已经到刷了 {progress_time}， 已经播放了{sum}秒。更新进度时间是: {Config.EVERY_OTHER_PROGRESSVIDEOTIME}, 已经累加进度{progress_time}")
                if sum == Config.EVERY_OTHER_PROGRESSVIDEOTIME:
                    """每N秒更新一次, 请看配置类， 更新完毕后重置更新时间, 并更新数据库"""
                    headers = {
                        "Host": "apif.wozhipei.com",
                        "Origin": "https://jiangxi.zhipeizaixian.com",
                        "Sec-Fetch-Dest": "empty",
                        "Sec-Fetch-Mode": "cors",
                        "Sec-Fetch-Site": "cross-site",
                        "X-Client-Type": "pc",
                        "X-Site-Alias": "jx",
                        "X-User-Type": "1",
                    }
                    newHeaders = {**self.session.headers, **headers}

                    response = self.guaranteeRequest("POST", self.UrlPool.get("updateProgress").format(
                        video_id=info.get("video_id"),
                        u=info.get("u"),
                        time=progress_time,
                        unit_id=info.get("id"),
                        class_id=info.get("class_id")
                    ),  headers=newHeaders)

                    print(f"进度发送成功! info: {info}", response.content.decode("utf-8"))
                    res = response.json()
                    if res['code'] == 555:
                        self.update_Video_data(
                            {
                                "idCar": self.idCar,
                                "video_id": info.get("video_id"),
                                "progress_time": progress_time
                            }
                        )
                        self.update_sleep_time({ #更新视频进度时间
                            "time": time.time(),
                            "video_id": video_id,
                            "idCar": self.idCar
                        })
                    else:
                        self.logger.info(f"{info.get('title')}更新失败 VideoId={info.get('video_id')}")

                    sum = 0
                    self.logger.info(f"数据库已经更新到{progressTime}分钟, 当前秒数已经重置")

        unDoninfos = self.querAllProgressUndonInfo(self.idCar) # 获取所有未完成的课程
        if len(unDoninfos) > 0:
            for undone in unDoninfos:
                print(undone)
                video_id = undone.get("video_id")
                u = undone.get("uid")
                id = undone.get("id")
                class_id = undone.get("class_id")

                title = undone.get("title")
                progress_time = int(undone.get("progress_time"))
                total_time = int(undone.get("total_time"))
                info = {
                    "video_id":video_id,
                    "u":u,
                    "id":id,
                    "class_id":class_id,
                    "total_time": total_time,
                    "progress_time": progress_time,
                    "title": title
                }

                queryDate = self.query_sleep_time({
                    "video_id": video_id,
                    "idCar": self.idCar
                })

                if len(queryDate) <= 0:
                    # 如果没有的话就插入
                    self.set_sleep_time({
                        "time": time.time(),
                        "video_id":video_id,
                        "idCar": self.idCar
                    })

                setProgressVideo(info)
                self.updateAccessToken() # 观看完一个视频后刷新token时间

    @check_file_and_redis(1, "")
    @calculate_execution_time(1, "刷新token信息")
    def updateAccessToken(self):
        """刷新token信息"""
        headers = {
            "Content-Type": "application/json",
            "Origin": "https://admin-jiangxi.zhipeizaixian.com",
            "Accept": "application/json, text/plain, */*",
            "Accept-encoding": "gzip, deflate, br, zstd",
            "Accept-language": "zh-CN,zh;q=0.9",
            "Cache-control": "no-cache",
            "Referer": "https://admin-jiangxi.zhipeizaixian.com/train-center/mine/student/subPages/student/class",
            "Authorization-platform": self.accessToken
        }
        nowHeaders = {**self.session.headers, **headers}
        response = self.guaranteeRequest("POST", self.UrlPool.get("updateTokenUrl"), headers=nowHeaders, data={})
        if response.json() == True:
            return response
        else:
            self.logger.debug(f"当前: 刷新token失败 响应结果:{response.json()}\n请求头:{nowHeaders} 正在等待5秒后重试....")
            self.unifiedErrorSleep()
            self.updateAccessToken()




if __name__ == '__main__':
    flash = FlashClass("360728200305202539", "202539", "123")
    publicKey = flash.getPublicKey()
    token = flash.getLoginToken(publicKey)
    flash.updateAccessToken()
    # flash.setSessionHeaders({
    #     'User-Agent': random.choice(generate_user_agents(100)),
    #     'Authorization': "Bearer eyJhbGciOiJIUzM4NCJ9.eyJqdGkiOiJqd3RfaWQiLCJzdWIiOiIxNzI5NjEyMEIxSEZZWEE4IiwiaWF0IjoxNzMwODgxODA0LCJhdWQiOiJXRUIiLCJleHAiOjE3MzE0ODY5MDQsInNpZCI6MTAxOCwidHlwZSI6IlVTRVIiLCJncm91cFR5cGUiOjF9.FTvnTL64kdazAZ0yrqoXSaewVRCoTwkR6DVxl7_koMPPgsFwo-46k0ESNMpyJgwK"
    # })
    classInfo = flash.getStudentStudy()
    flash.getAllcoursesPreview(classInfo)
    flash.updateProgress()
